﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.IO;
using Microsoft.Xna.Framework.Graphics;

namespace TrafficTrouble.Core
{
    public enum LevelCompletionState { None, Incomplete, Completed }
    public class Grid
    {
        public LevelCompletionState CompletionState { get; set; }

        public LevelDifficulty Difficulty { get; private set; }
        public int Number { get; private set; }

        public int Size { get; private set; }

        public Car[] Cars { get; private set; }

        public Grid(LevelDifficulty difficulty, int number, int size, params Car[] cars)
        {
            this.Difficulty = difficulty;
            this.Number = number;

            this.Size = size;

            this.Cars = cars;

            this.CompletionState = LevelCompletionState.None;
        }
        public Grid(LevelDifficulty difficulty, int number, LevelCompletionState completionState, int size,
            params Car[] cars)
            : this(difficulty, number, size, cars)
        {
            this.CompletionState = completionState;
        }

        public Car GetCarAt(int x, int y)
        {
            foreach (Car car in this.Cars)
                if (car.ContainsLocation(x, y))
                    return car;
            return null;
        }
        public bool IsEmpty(int x, int y)
        {
            return this.GetCarAt(x, y) == null;
        }
        public bool ContainsCar(int x, int y)
        {
            return !this.IsEmpty(x, y);
        }
        public void AddCar(Car car)
        {
            List<Car> cars = new List<Car>(this.Cars);
            cars.Add(car);
            this.Cars = cars.ToArray();
        }

        public Tuple<int, int> GetEmptyExtentsInInRow(Car car)
        {
            int lowX = car.X, highX = car.X;
            Car carAt = null;
            while (lowX > 0 && ((carAt = this.GetCarAt(lowX - 1, car.Y)) == car || carAt == null)) lowX--;
            while (highX < this.Size - 1 && ((carAt = this.GetCarAt(highX + 1, car.Y)) == car || carAt == null)) highX++;

            return new Tuple<int, int>(lowX, highX);
        }
        public Tuple<int, int> GetEmptyExtentsInColumn(Car car)
        {
            int lowY = car.Y, highY = car.Y;
            Car carAt = null;
            while (lowY > 0 && ((carAt = this.GetCarAt(car.X, lowY - 1)) == car || carAt == null)) lowY--;
            while (highY < this.Size - 1 && ((carAt = this.GetCarAt(car.X, highY + 1)) == car || carAt == null)) highY++;

            return new Tuple<int, int>(lowY, highY);
        }

        public override string ToString()
        {
            string grid = "";
            for (int y = 0; y < this.Size; y++)
                grid += new string('.', this.Size);

            char currentPlayerCarChar = 'A';
            char currentCarChar = 'a';
            foreach (Car car in this.Cars)
            {
                foreach (Point carLoc in car.GetContainingLocations())
                    grid = grid.ReplaceCharAt(carLoc.X + this.Size * carLoc.Y,
                        car.PlayerCar ? currentPlayerCarChar : currentCarChar);
                if (car.PlayerCar) currentPlayerCarChar++;
                else currentCarChar++;
            }

            grid = grid.Insert(0, "+" + new string('-', this.Size) + "+\n");
            for (int y = 0; y < this.Size; y++)
            {
                grid = grid.Insert((2 + this.Size) * (y + 1), "|");
                grid = grid.Insert((2 + this.Size) * (y + 1) + 1 + this.Size, "|");
            }
           grid += "+" + new string('-', this.Size) + "+";

            return grid.ToString();
        }
        public static bool operator ==(Grid g1, Grid g2)
        {
            return g1.ToString() == g2.ToString();
        }
        public static bool operator !=(Grid g1, Grid g2)
        {
            return !(g1 == g2);
        }
        public override bool Equals(object obj)
        {
            if (!(obj is Grid)) return false;
            return this == (Grid)obj;
        }
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public static Grid FromFile(LevelDifficulty difficulty, int number)
        {
            return Grid.FromFile(difficulty + "/" + difficulty + number);
        }
        public static Grid FromFile(string levelFileName)
        {
            Grid level = null;
            using (LevelFileStream levelFileStream = LevelFileStream.GetLevelStream(levelFileName))
            {
                Stream stream = levelFileStream.FileStream;
                using (StreamReader reader = new StreamReader(stream))
                {
                    level = Grid.FromStream(reader, levelFileStream.FormatVersion);
                }
            }
            return level;
        }
        public static Grid FromStream(StreamReader reader, byte formatVersion)
        {
            Grid level = null;

            if (formatVersion == 1)
            {
                string[] levelInfo = reader.ReadLine().Split(' ');
                LevelDifficulty difficulty = (LevelDifficulty)Enum.Parse(typeof(LevelDifficulty),
                    levelInfo[0], true);
                int levelNumber = int.Parse(levelInfo[1]);

                string[] gridInfo = reader.ReadLine().Split(' ');
                int levelSize = int.Parse(gridInfo[0]),
                    numTotalCars = int.Parse(gridInfo[1]),
                    numPlayerCars = int.Parse(gridInfo[2]);

                level = new Grid(difficulty, levelNumber, levelSize);

                for (int i = 0; i < numTotalCars; i++)
                {
                    string[] carInfo = reader.ReadLine().Split(' ');
                    int carX = int.Parse(carInfo[0]),
                        carY = int.Parse(carInfo[1]),
                        carLength = int.Parse(carInfo[2]);
                    bool vertical = carInfo[3].ToLower().StartsWith("v");

                    level.AddCar(new Car(level, (byte)i, carLength, vertical, carX, carY, (i < numPlayerCars)));
                }
            }
            else throw new Exception("Unknown level format version number '" + formatVersion
                    + ". (TrafficTrouble.Core.Grid.FromFile(StreamReader, byte)).");

            return level;
        }

        public void WriteToStream(StreamWriter writer, byte formatVersion)
        {
            // <FormatVersion = 1>
            // <LevelDifficulty> <LevelNumber>
            // <LevelWidth> <LevelHeight> <#TotalCars> <#PlayerCars>
            // <CarX> <CarY> <CarLength> <V/H> (* #PlayerCars)
            // <CarX> <CarY> <CarLength> <V/H> (* (#TotalCars - #PlayerCars))

            int numPlayerCars = 0;
            foreach (Car car in this.Cars)
                if (car.PlayerCar)
                    numPlayerCars++;

            if (formatVersion == 1)
            {
                writer.WriteLine(this.Difficulty + " " + this.Number);
                writer.WriteLine(this.Size + " " + this.Cars.Length + " " + numPlayerCars);

                foreach (Car car in this.Cars)
                    if (car.PlayerCar)
                        writer.WriteLine(car.X + " " + car.Y + " " + car.Length + " " + (car.Vertical ? "V" : "H"));
                foreach (Car car in this.Cars)
                    if (!car.PlayerCar)
                        writer.WriteLine(car.X + " " + car.Y + " " + car.Length + " " + (car.Vertical ? "V" : "H"));
            }
            else throw new Exception("Unknown level format version number '" + formatVersion
                + ". (TrafficTrouble.Core.Grid.WriteToStream(StreamWriter, byte)).");
        }

        public static int GetNumLevelsOfDifficulty(LevelDifficulty difficulty)
        {
            switch (difficulty)
            {
                case LevelDifficulty.Beginner: return 1;
                case LevelDifficulty.Easy: return 1;
                case LevelDifficulty.Medium: return 1;
                case LevelDifficulty.Hard: return 1;
                case LevelDifficulty.Expert: return 1;
                case LevelDifficulty.Master: return 1;
                default:
                    throw new Exception("Forgot a LevelDifficulty in Grid.GetNumLevelsOfDifficulty(LevelDifficulty).");
            }
        }

        public void RenderToTarget(SpriteBatch g, Vector2 renderTargetSize, Color backgroundColor)
        {
            g.GraphicsDevice.Clear(backgroundColor);
            g.Begin();
            float squareSize = (renderTargetSize / (float)this.Size).X;
            foreach (Car car in this.Cars)
            {
                Texture2D carTex = ((TrafficTrouble.States.GameState)JGLib.States.State.States[typeof(TrafficTrouble.States.GameState)])
                    .GetCarTexture(/*car.Length*/2, car.PlayerCar);
                //                 ^ ^ ^ ^ ^ ^ ^
                //                 | | | | | | |
                // Change the '2' to 'car.Length' 3 lines above this and delete these 3 comments once more car images are 'found'.
                Vector2 squareTopLeft = squareSize * new Vector2(car.GridCoords.X, car.GridCoords.Y);

                if (car.Horizontal)
                {
                    g.Draw(carTex, new Rectangle((int)squareTopLeft.X, (int)squareTopLeft.Y,
                            (int)(car.Length * squareSize), (int)squareSize),
                        Color.White);
                }
                else
                {
                    // draw rotated 90 degrees to the right. (left?... whatever)
                    g.Draw(carTex, new Rectangle((int)squareTopLeft.X, (int)squareTopLeft.Y,
                            (int)(car.Length * squareSize), (int)squareSize),
                        null,
                        Color.White,
                        MathHelper.PiOver2,
                        new Vector2(0, carTex.Height),
                        SpriteEffects.None,
                        0);
                }
            }
            g.End();
        }
    }
}
